﻿using System;
using System.Threading;
using Gadgeteer.Interfaces;
using Gadgeteer.Modules.Sytech.Camera;
using Gadgeteer.Modules.Sytech.CameraProtocol;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GTI = Gadgeteer.Interfaces;

namespace Gadgeteer.Modules.Sytech
{
    /// <summary>
    /// A Camera Gadgeteer module
    /// 
    /// This is initial code and still under development...
    /// 
    /// 
    /// Not all features added yet.
    /// Currently supports :
    /// Setting resolution to VGA(640 320), QVGA(320 240) and Thumb (160x 120) see <see cref= "Resolution"/> 
    /// The default power up resolution is always QVGA ( 320 x 240)
    /// 
    /// Allows picture to be triggered when button is pressed or
    ///  <see cref="TakePicture"/> can be called by the application.
    /// 
    /// There is an <see cref= "OnPictureProgress"/> event, which is fired during the 
    /// process of downloading a picture from the camera - with the download progress.
    /// 
    /// There is an <see cref="CameraPictureReady"/> event, fired when the picture has
    /// been downloaded, the event args contain the image as a Gadgeteer.Picture, the encoding type
    /// of the picture buffer and the image resolution.
    /// The eventArgs have properies to return the image as a buffer, for use in saving to SD card etc. and also as 
    /// a bitmap, for displaying directly on the display. See <see cref= "ImageEventArg"/>
    /// 
    /// A new picture cannot be taken until the current picture is complete.
    /// 
    /// After creation, the camera must be enabled. When the initalisation process has finished and the 
    /// camera is ready for use the event <see cref= "CameraReady"/> is fired.
    /// 
    /// 
    /// Tob added:
    /// Support for jpeg compression ration setting
    /// Support for changing the connection Baud rate
    /// </summary>
    /// 
    /// <example>
    /// <para>The following example show intialization of a <see cref="SerialCamera"/> object </para>
    /// <code>
    /// using System;
    /// using Microsoft.SPOT;
    /// using Microsoft.SPOT.Presentation;
    /// using Microsoft.SPOT.Presentation.Controls;
    /// using Microsoft.SPOT.Presentation.Media;
    ///
    /// using GT = Gadgeteer;
    /// using GTM = Gadgeteer.Modules;
    ///
    /// using Gadgeteer.Modules.Sytech;
    ///
    /// namespace GadgeteerAppGettingStarted
    /// {
    ///     public partial class Program
    ///     {
    ///         // This template uses the NANAO mainboard from Sytech Designs
    ///
    ///         // Define and initialize GTM.Modules here, specifying their socket numbers.   
    ///         // Needs a USB Device module for connection to VS and suppying power     
    ///         GTM.Sytech.UsbDevice usbClient = new UsbDevice(1);
    ///         
    ///         // Use an button module to trigger pictures - can use on board button too
    ///         GTM.Sytech.Button button = new Button(3);
    /// 
    ///         // Dispaly to show picture
    ///         GTM.Sytech.LCDTouch display = new  LCDTouch(10,9,8);
    ///         // now add the camera
    ///         GTM.Sytech.SerialCamera camera = new SerialCamera(2);
    ///
    ///         void ProgramStarted()
    ///         {
    ///             // Initialize event handlers here.
    ///             // Set up the 'shutter' button in the event handler - example of using an external button to trigger
    ///             // picture from the application
    ///             // The button on the camera module is also automatically connected to trigger pictures
    ///             button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed);
    /// 
    ///             // now connect camera events
    ///             camera.OnPictureProgess += new GTM.Sytech.Camera.PictureProgressDel(SerialCamera_OnPictureProgess);
    ///             camera.CameraPictureReady += new SerialCamera.CameraEventHandler(SerialCamera_CameraPictureReady);
    ///            camera.CameraReady += new SerialCamera.CameraEnabledEventHandler(camera_CameraReady);
    ///             
    ///             // enable the camera
    ///             camera.EnableCamera();
    /// 
    ///             
    /// 
    ///             
    ///             Debug.Print("Program Started");
    ///         }
    ///
    ///         void button_ButtonPressed(Button sender, Button.ButtonState state)
    ///         {
    ///             // how to trigger a picture from the application
    ///             camera.TakePicture();
    ///         }
    /// 
    ///         // Example of an event handler for Camera Ready, changes resolution
    ///         void camera_CameraReady(SerialCamera sender, ImageSize resolution)
    ///         {
    ///             camera.Resolution = ImageSize.vga;
    ///             Debug.Print("Camera resolution set to vga");
    ///         }
    ///         
    ///         //Event handler for progress event
    ///         //Just show data on debug output
    ///        void SerialCamera_OnPictureProgess(object sender, ProgressEventArg arg)
    ///        {
    ///            Debug.Print("Picture progress size : " + arg.size + " blocks:" + arg.Blocks + " this block:" + arg.blockReceived);
    ///        }
    /// 
    ///         // Example of event handler for Picture ready
    ///         void SerialCamera_CameraPictureReady(SerialCamera sender, ImageEventArg image)
    ///         {
    ///             // Use event args to return the image as a bitmap
    ///             Bitmap bmp = CameraImage.GetBitmap();
    ///  
    ///             if (bmp != null)
    ///             {
    ///                  display.SimpleGraphics.DisplayImage(bmp,5,5);
     ///            }
     ///            else
     ///            {
     ///                Debug.Print("Image to bitmap conversion error");
     ///            }
     ///        
    ///             // Example of getting the image buffer, this can be used to write the image to a file
     ///            byte[] buffer = CameraImage.GetImageDataBuffer();
    ///         }
    ///
    /// 
    /// 
    ///     }
    /// }
    ///
    /// </code>
    /// </example>
    public class SerialCamera : GTM.Module
    {

        private Camera0706 m_camera;
        private Gadgeteer.Interfaces.Serial m_serial;
        private Socket socket;
        private InterruptInput button;

        private Timer m_stateTimer;

        private bool m_enabled = false;

        private initState startup = initState.powerUp;

        private Thread pictureThread;
        private CameraState state = CameraState.Ready;

        private ImageSize m_resolution;

        

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socketNumber">The socket that this module is plugged in to.</param>
        public SerialCamera(int socketNumber)
        {
            // This finds the Socket instance from the user-specified socket number.  
            // This will generate user-friendly error messages if the socket is invalid.
            // If there is more than one socket on this module, then instead of "null" for the last parameter, 
            // put text that identifies the socket to the user (e.g. "S" if there is a socket type S)
            Socket socket = Socket.GetSocket(socketNumber, true, this, null);
          
            //Validate the socket
            socket.EnsureTypeIsSupported(new char[]{'K','U'},this);

            // This creates an GTI.InterruptInput interface. The interfaces under the GTI namespace provide easy ways to build common modules.
            // This also generates user-friendly error messages automatically, e.g. if the user chooses a socket incompatible with an interrupt button.
            this.button = new GTI.InterruptInput(socket, GT.Socket.Pin.Three, GTI.GlitchFilterMode.On, GTI.ResistorMode.PullUp, GTI.InterruptMode.FallingEdge, this);

            //// This registers a handler for the interrupt event of the interrupt button (which is below)
            this.button.Interrupt += new GTI.InterruptInput.InterruptEventHandler(this._button_Interrupt);
        

            // get socket serial port - default to 38400 for now
            // make this configurable  - call made by app to configure 36400
            m_serial = new Serial(socket, 38400, Serial.SerialParity.None, Serial.SerialStopBits.One, 8, Serial.HardwareFlowControl.NotRequired, this);
            m_camera = new Camera0706(m_serial);

            m_stateTimer = new Timer(2000,Timer.BehaviorType.RunOnce);
            m_stateTimer.Tick += new Timer.TickEventHandler(m_stateTimer_Tick);
            //not enabled yet so..
            state = CameraState.Busy;
        }

        


        /// <summary>
        ///  to be implemented, configure baud rate and picture 
        /// resolution - instead of in constructor
        /// </summary>
        public void ConfigureCamera()
        {
            
        }

        #region Enable Camera

        /// <summary>
        /// Enable and setup the camera.
        /// Can only be called once.
        /// Setup happens on a background thread and takes
        /// about 4 seconds
        /// Must be called before camera can be used
        /// </summary>
        public void EnableCamera()
        {
            //call once
            if (m_enabled) return;

            m_enabled = true;
            // flush buffers in case we have the camera power on string
            m_camera.Flush();
            // intial camera reset code - will be refined!
            m_camera.DoReset();
            startup = initState.config;
            m_stateTimer.Start();
     

        }

        
        
        /// <summary>
        /// Eanble camera state machine
        /// </summary>
        /// <param name="timer"></param>
        void m_stateTimer_Tick(Timer timer)
        {
           
            switch (startup)
            {
                case initState.powerUp:
                    m_camera.Flush();
                    m_stateTimer.Start();
                    m_resolution = ImageSize.svga;
                    break;
                case initState.config:
                    
                    startup = initState.ready;
                    m_camera.Flush();

                    //set the resolution in ram
                    // always start with 1/4VGA SNT - default do we need this
                  //   m_camera.SetImageSize(ImageSize.svga);
                    
                    Thread.Sleep(1000);
                    m_stateTimer.Start();
              

                    break;
                case initState.ready:
                    
                    m_camera.Flush();
                    m_camera.OnProgress += new PictureProgressDel(m_camera_OnProgress);
                    m_camera.SetImageSize(ImageSize.svga);
                    DebugPrint("Camera Ready");

                    //camera ready
                    state = CameraState.Ready;

                    if (CameraReady != null)
                    {
                        CameraReady(this, m_resolution);
                    }
                    // fire ready event?
                    break;
            }
           
        }

        #endregion

        #region public methods

        /// <summary>
        /// Take a picture.
        /// Only one picture can be processed at a time.
        /// So no further calls aactioned until after the current]
        /// one is handled.
        /// 
        /// Picture progress happens on a background thread.
        /// There are OnProgress Events <see cref="OnPictureProgess"/> and an event
        /// is fired when the picture is ready <see cref="CameraPictureReady"/>
        /// </summary>
        public void TakePicture()
        {

            if (state == CameraState.Busy)
            {

                DebugPrint("Camera is busy");

            }
            else
            {

                pictureThread = new Thread(GetPicture);
                pictureThread.Start();

                DebugPrint("Take Picture");


            }

        }

        #endregion


        #region properties

        /// <summary>
        /// Gets a value that indicates whether the state of this Camera button is high.
        /// </summary>
        public bool IsHigh
        {
            get
            {
                return this.button.Read();
            }
        }

        /// <summary>
        /// Read or write the Camera resolution.
        /// This can be VGA, QVGA or Thumb
        /// Size is enum <see cref="ImageSize" />
        /// </summary>
        public ImageSize Resolution
        {
            set { 
                    if (m_camera.SetImageSize(value))
                     m_resolution = value  ; 
            }
            get { return m_resolution; }
        
        }

        #endregion

        #region Events

        /// <summary>
        /// Represents the delegate that is used to handle the <see cref="CameraReady"/>
        /// events.
        /// </summary>
        /// <param name="sender">The <see cref="Camera"/> object that raised the event.</param>
        /// <param name="resolution">The current camera resolution</param>
        public delegate void CameraEnabledEventHandler(SerialCamera sender, ImageSize resolution);



        /// <summary>
        /// Event fired when the camera has been initialise and is
        /// ready for use
        /// </summary>
        public event CameraEnabledEventHandler CameraReady;


        /// <summary>
        /// Picture progress Event.
        /// This is synched to the GUI thread and 
        /// is fired for every block of data downloaded
        /// from the camera.
        /// </summary>
        public event PictureProgressDel OnPictureProgess;
        private PictureProgressDel onProgress;


        /// <summary>
        /// Camera Protocol progress event handler.
        /// Forwards the event to the application if used.
        /// Application level event is marshalled onto the 
        /// GUI thread
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="arg"></param>
        void  m_camera_OnProgress(object sender, ProgressEventArg arg)
        {   

           if (this.onProgress == null)
           {
               this.onProgress = new PictureProgressDel(this.m_camera_OnProgress);
           }

           if (Program.CheckAndInvoke(OnPictureProgess, this.onProgress, sender, arg))
           {
               DebugPrint("FileSize :" + arg.size + " Blocks :" + arg.Blocks + " RXBlock: " + arg.blockReceived);
               if (OnPictureProgess != null)
               {
                   OnPictureProgess(this, arg);
               }
           }
        }

        /// <summary>
        /// Represents the delegate that is used to handle the <see cref="CameraPictureReady"/>
        /// events.
        /// </summary>
        /// <param name="sender">The <see cref="SerialCamera"/> object that raised the event.</param>
        /// <param name="CameraImage">The image from the Camera</param>
        public delegate void CameraEventHandler(SerialCamera sender, ImageEventArg CameraImage);



        /// <summary>
        /// Event fired when the current picture is ready.
        /// jpeg picture passed in the event.
        /// Event is fired on the GUI thread
        /// </summary>
        public event CameraEventHandler CameraPictureReady;

        private CameraEventHandler onCamera;

        /// <summary>
        /// Raises the <see cref="CameraPictureReady"/>  event.
        /// </summary>
        /// <param name="sender">The <see cref="SerialCamera"/> that raised the event.</param>
        /// <param name="CameraImage">The Camera Image .</param>
        protected virtual void OnCameraEvent(SerialCamera sender, ImageEventArg CameraImage)
        {
            if (this.onCamera == null)
            {
                this.onCamera = new CameraEventHandler(this.OnCameraEvent);
            }

            if (Program.CheckAndInvoke(CameraPictureReady, this.onCamera, sender, CameraImage))
            {
                if (CameraPictureReady != null)
                {
                    CameraPictureReady(this, CameraImage);
                }
            }
        }

        #endregion


       
        
        /// <summary>
        /// Button pressed handler
        /// </summary>
        /// <param name="input"></param>
        /// <param name="value"></param>
        private void _button_Interrupt(GTI.InterruptInput input, bool value)
        {
            
            DebugPrint("button Pressed");
           
            TakePicture();

        }

        

        
        /// <summary>
        /// Download a captured picture from the camera.
        /// 
        /// Download image in blocks. A progress event is fired after each block
        /// </summary>
        private  void GetPicture()
        {
            state = CameraState.Busy;
            m_camera.CaptureImage();

            int size = m_camera.ReadFileSize();
            DebugPrint("file size: " + size);
            byte[] picture;
            picture = m_camera.ReadBlockFile(size);

            // if file size large force gc to ensure enougth memory
            if (size > 15000)
            {
                Debug.GC(true);
            }
                //add the picture to the camera event
              
            ImageEventArg Image = new ImageEventArg(picture,Picture.PictureEncoding.JPEG,m_resolution);
            // Debug.Print("fire picture event");
            this.OnCameraEvent(this, Image);
               
            state = CameraState.Ready;
               
        }
        

       

     
       

        #region enums

        /// <summary>
        /// Represents the state of the <see cref="SerialCamera"/> object.
        /// </summary>
        public enum CameraState
        {
            /// <summary>
            /// The state of Camera is low.
            /// </summary>
            Ready = 0,
            /// <summary>
            /// The state of Camera is high.
            /// </summary>
            Busy = 1
        }

        private enum initState
        {
            powerUp,
            config,
            ready
        } ;

        #endregion
    }
}
